package com.atguigu.securitydemo1.config;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import javax.annotation.Resource;
import javax.sql.DataSource;

/**
 * 开发中的常用方式
 * 1.创建配置类,设置使用哪个对象userDetailService实现类
 * 2.编写实现类,返回User对象,User对象有用户名密码和操作权限。
 */
@Configuration
public class SecurityConfigUserAndPassword extends WebSecurityConfigurerAdapter {

    /**
     * 这里注入spring-security提供的用户接口UserDetailsService
     * 当什么也没有配置的时候，账号和密码是由 Spring Security 定义生成的。而在实际项目中
     * 账号和密码都是从数据库中查询出来的。 所以我们要通过自定义逻辑控制认证逻辑。
     * 创建自定义实现UserDetailsService的实现类,这里使用按名称注入,因为UserDetailsService的继承类太多了
     */
    @Resource
    UserDetailsService myUserDetailsService;

    //注入数据源
    @Autowired
    private DataSource dataSource;

    /**
     * token配置的流程是
     * 登录成功后,像客户端返回一个token值,同时像数据库插入一个toke值
     * 每次访问带上这个token,然后去数据库进行比对。
     * 这里是配置像数据插入token.
     * @return
     */
    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        // 自动创建token验证表,第一次启动服务会自动创建表,第二次重启会报该表已存在,所以这里注释掉
        // jdbcTokenRepository.setCreateTableOnStartup(true);
        return jdbcTokenRepository;
    }

    //2.先调用这个配置方法装载配置
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(myUserDetailsService)
                .passwordEncoder(passwordEncoder());
    }


    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }


    /**
     * 1.用户认证通过后,进行权限效验
     * 除了一些特殊指定的,都需要进行用户效验
     * 自定义设置登录页面
     * 不需要认证可以访问
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        //退出
        http.logout().logoutUrl("/logout")
                .logoutSuccessUrl("/test/hello").permitAll();

        //配置没有权限访问，然后跳转到自定义的页面unauth.html
        http.exceptionHandling().accessDeniedPage("/unauth.html");


        //自定义自己编写的登录页面
        http.formLogin()
                //登录页面设置
                .loginPage("/login.html") //登录页面设置
                //登录访问的路径,这个路径会自动被一个过滤器捕获到,然后将username和password传入到MyUserDetailsService
                .loginProcessingUrl("/user/login")
                .defaultSuccessUrl("/success.html").permitAll() //这里是登录成功之后,会跳转到哪里
                .and().authorizeRequests()
                .antMatchers("/", "/test/hello", "/user/login").permitAll() //设置哪些路径可以直接访问,不需要认证,其它的路径都会跳转到认证页面
                //基于角色或权限进行访问控制
                //1.当前登录用户,只有具有admins权限才可以访问这个路径
                .antMatchers("/test/index").hasAuthority("admins")
                //2.这个方法可以匹配多个角色
                // .antMatchers("/test/index").hasAnyAuthority("admins,manager")
                //3.hasRole方法 ROLE_sale, 这里再设置的时候,要在角色名前添加Role_
                // .antMatchers("/test/index").hasRole("sale")
                //4. hasanyRole方法表示用户具备任何一个条件都可以访问
                // .antMatchers("/test/index").hasAnyRole("admin,role,Role_admin,Role_role")
                .anyRequest().authenticated()
                //设置token,配置自动登录
                .and().rememberMe().tokenRepository(persistentTokenRepository())
                .tokenValiditySeconds(60) //设置有效时长,单位秒
                .userDetailsService(myUserDetailsService)
                .and().csrf().disable();  //关闭csrf防护
    }
}
